using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Collections;

#if CF_1_0 || CF_2_0 || CF_3_5
using RaisingStudio.Reflection.Emit;
#else
using System.Reflection.Emit;
#endif

namespace RaisingStudio.Data.Common.Factories
{
    public class DynamicObjectAccessorFactory
    {
        public delegate string GetAssemblyNameDelegate(System.Type type, string[] propertyNames);
        public static GetAssemblyNameDelegate GetAssemblyName { get; set; }

        public static System.Type GetPropertyType(System.Type type, string propertyName, out System.Reflection.MemberInfo memberInfo)
        {
            memberInfo = null;
            System.Reflection.PropertyInfo propertyInfo = type.GetProperty(propertyName);
            if (propertyInfo != null)
            {
                memberInfo = propertyInfo;
                System.Type propertyType = propertyInfo.PropertyType;
                return propertyType;
            }
            else
            {
                System.Reflection.FieldInfo fileInfo = type.GetField(propertyName);
                if (fileInfo != null)
                {
                    memberInfo = fileInfo;
                    System.Type fileType = fileInfo.FieldType;
                    return fileType;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("propertyName");
                }
            }
        }

        public static void EmitFastInt(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    return;
            }

            if (value > -129 && value < 128)
            {
                il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
            }
            else
            {
                il.Emit(OpCodes.Ldc_I4, value);
            }
        }

        public IObjectAccessor CreateObjectAccessor(System.Type type, string[] propertyNames, out Dictionary<string, System.Type> propertyTypes)
        {
            propertyTypes = new Dictionary<string, System.Type>();

            AssemblyName assemblyName = new AssemblyName();
            if (GetAssemblyName != null)
            {
                assemblyName.Name = GetAssemblyName(type, propertyNames);
            }
            else
            {
                assemblyName.Name = System.Guid.NewGuid().ToString();
            }
            AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);

            TypeBuilder typeBuilder = moduleBuilder.DefineType("_", TypeAttributes.Public, null, new Type[] { typeof(IObjectAccessor), typeof(IObjectCreater) });
            typeBuilder.AddInterfaceImplementation(typeof(IObjectAccessor));
            typeBuilder.AddInterfaceImplementation(typeof(IObjectCreater));

            ConstructorBuilder constructorBuilder = typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
            #region CreateObject
            MethodBuilder createObjectMethodBuilder = typeBuilder.DefineMethod("CreateObject", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, CallingConventions.Standard, typeof(object), null);
            ILGenerator createObjectILGenerator = createObjectMethodBuilder.GetILGenerator();
            /*
            L_0000: newobj instance void [__Type.Assembly__]__Type__::.ctor()
            L_0005: ret 
            */
            createObjectILGenerator.Emit(OpCodes.Newobj, type.GetConstructor(new System.Type[0]));
            createObjectILGenerator.Emit(OpCodes.Ret);
            #endregion
            #region CreateObjectList
            MethodBuilder createObjectListMethodBuilder = typeBuilder.DefineMethod("CreateObjectList", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, CallingConventions.Standard, typeof(IList), null);
            ILGenerator createObjectListILGenerator = createObjectListMethodBuilder.GetILGenerator();
            /*
            L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<class [__Type.Assembly__]__Type__>::.ctor()
            L_0005: ret 
            */
            createObjectListILGenerator.Emit(OpCodes.Newobj, (typeof(List<>).MakeGenericType(type)).GetConstructor(new System.Type[0]));
            createObjectListILGenerator.Emit(OpCodes.Ret);
            #endregion

            #region GetValue
            MethodBuilder getValueMethodBuilder = typeBuilder.DefineMethod("GetValue", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot | MethodAttributes.HideBySig, CallingConventions.Standard, typeof(object), new Type[] { typeof(object), typeof(string) });
            ILGenerator getValueILGenerator = getValueMethodBuilder.GetILGenerator();
            /*
            L_0001: ldarg.1 
            L_0002: isinst __Type__
            L_0007: stloc.0 
            */
            getValueILGenerator.Emit(OpCodes.Ldarg_1);
            getValueILGenerator.Emit(OpCodes.Isinst, type);
            LocalBuilder getValue_local_0 = getValueILGenerator.DeclareLocal(type);
            getValueILGenerator.Emit(OpCodes.Stloc, getValue_local_0);
            /*
            L_0008: newobj instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::.ctor()
            L_000d: stloc.1 
            */
            getValueILGenerator.Emit(OpCodes.Newobj, typeof(CommonDictionary).GetConstructor(new System.Type[0]));
            LocalBuilder getValue_local_1 = getValueILGenerator.DeclareLocal(typeof(CommonDictionary));
            getValueILGenerator.Emit(OpCodes.Stloc, getValue_local_1);
            #endregion
            #region SetValue
            MethodBuilder setValueMethodBuilder = typeBuilder.DefineMethod("SetValue", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot | MethodAttributes.HideBySig, CallingConventions.Standard, null, new Type[] { typeof(object), typeof(string), typeof(object) });
            ILGenerator setValueILGenerator = setValueMethodBuilder.GetILGenerator();
            /*
            L_0001: ldarg.1 
            L_0002: isinst __Type__
            L_0007: stloc.0 
            */
            setValueILGenerator.Emit(OpCodes.Ldarg_1);
            setValueILGenerator.Emit(OpCodes.Isinst, type);
            LocalBuilder setValue_local_0 = setValueILGenerator.DeclareLocal(type);
            setValueILGenerator.Emit(OpCodes.Stloc, setValue_local_0);
            /*
            L_0008: newobj instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::.ctor()
            L_000d: stloc.1 
            */
            setValueILGenerator.Emit(OpCodes.Newobj, typeof(CommonDictionary).GetConstructor(new System.Type[0]));
            LocalBuilder setValue_local_1 = setValueILGenerator.DeclareLocal(typeof(CommonDictionary));
            setValueILGenerator.Emit(OpCodes.Stloc, setValue_local_1);
            #endregion
            int index = 0;
            List<MemberInfo> members = new List<MemberInfo>();
            foreach (string propertyName in propertyNames)
            {
                System.Reflection.MemberInfo memberInfo;
                System.Type propertyType = GetPropertyType(type, propertyName, out memberInfo);
                propertyTypes.Add(propertyName, propertyType);
                members.Add(memberInfo);

                #region GetValue
                /*
                L_000e: ldloc.1     
                L_000f: ldstr {name}
                L_0014: ldc.i4.0 
                L_0015: callvirt instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
                */
                getValueILGenerator.Emit(OpCodes.Ldloc, getValue_local_1);
                getValueILGenerator.Emit(OpCodes.Ldstr, propertyName);
                EmitFastInt(getValueILGenerator, index);
                getValueILGenerator.Emit(OpCodes.Call, (typeof(CommonDictionary)).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance));
                #endregion
                #region SetValue
                /*
                L_000e: ldloc.1     
                L_000f: ldstr {name}
                L_0014: ldc.i4.0 
                L_0015: callvirt instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
                */
                setValueILGenerator.Emit(OpCodes.Ldloc, setValue_local_1);
                setValueILGenerator.Emit(OpCodes.Ldstr, propertyName);
                EmitFastInt(setValueILGenerator, index);
                setValueILGenerator.Emit(OpCodes.Call, (typeof(CommonDictionary)).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance));
                #endregion

                index++;
            }

            #region GetValue
            Label getValue_Switch_Label = getValueILGenerator.DefineLabel();
            Label getValue_Exception_Label = getValueILGenerator.DefineLabel();
            LocalBuilder getValue_local_num = getValueILGenerator.DeclareLocal(typeof(int));
            /*
             L_0035: ldloc.1 
             L_0036: ldarg.2 
             L_0037: ldloca.s 'value'
             L_0039: callvirt instance bool [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::TryGetValue(!0, !1&)
             L_003e: ldc.i4.0 
             L_003f: ceq 
             L_0045: brtrue.s L_0084
             */
            getValueILGenerator.Emit(OpCodes.Ldloc, getValue_local_1);
            getValueILGenerator.Emit(OpCodes.Ldarg_2);
            getValueILGenerator.Emit(OpCodes.Ldloca_S, getValue_local_num);
            getValueILGenerator.Emit(OpCodes.Call, (typeof(CommonDictionary)).GetMethod("TryGetValue", BindingFlags.Public | BindingFlags.Instance));
            getValueILGenerator.Emit(OpCodes.Brtrue, getValue_Switch_Label);
            getValueILGenerator.Emit(OpCodes.Br, getValue_Exception_Label);
            #endregion
            #region SetValue
            Label setValue_Switch_Label = setValueILGenerator.DefineLabel();
            Label setValue_Exception_Label = setValueILGenerator.DefineLabel();
            LocalBuilder setValue_local_num = setValueILGenerator.DeclareLocal(typeof(int));
            /*
           L_0035: ldloc.1 
           L_0036: ldarg.2 
           L_0037: ldloca.s 'value'
           L_0039: callvirt instance bool [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::TryGetValue(!0, !1&)
           L_003e: ldc.i4.0 
           L_003f: ceq 
           L_0045: brtrue.s L_0084
            */
            setValueILGenerator.Emit(OpCodes.Ldloc, setValue_local_1);
            setValueILGenerator.Emit(OpCodes.Ldarg_2);
            setValueILGenerator.Emit(OpCodes.Ldloca_S, setValue_local_num);
            setValueILGenerator.Emit(OpCodes.Call, (typeof(CommonDictionary)).GetMethod("TryGetValue", BindingFlags.Public | BindingFlags.Instance));
            setValueILGenerator.Emit(OpCodes.Brtrue, setValue_Switch_Label);
            setValueILGenerator.Emit(OpCodes.Br, setValue_Exception_Label);
            #endregion

            List<Label> getValue_Label_List = new List<Label>();
            List<Label> setValue_Label_List = new List<Label>();
            foreach (System.Reflection.MemberInfo memberInfo in members)
            {
                #region GetValue
                /*
                L_0061: ldloc.0 
                L_0062: callvirt instance {PropertyType} {Type}::{Property}()
                L_0067: box {PropertyType}                 
                 */
                Label getValue_Label = getValueILGenerator.DefineLabel();
                getValueILGenerator.MarkLabel(getValue_Label);
                getValueILGenerator.Emit(OpCodes.Ldloc, getValue_local_0);
                if (memberInfo is PropertyInfo)
                {
                    PropertyInfo propertyInfo = memberInfo as PropertyInfo;
                    getValueILGenerator.Emit(OpCodes.Callvirt, propertyInfo.GetGetMethod());
                    if (propertyInfo.PropertyType.IsValueType)
                    {
                        getValueILGenerator.Emit(OpCodes.Box, propertyInfo.PropertyType);
                    }
                }
                else
                {
                    FieldInfo fieldInfo = memberInfo as FieldInfo;
                    getValueILGenerator.Emit(OpCodes.Ldfld, fieldInfo);
                    if (fieldInfo.FieldType.IsValueType)
                    {
                        getValueILGenerator.Emit(OpCodes.Box, fieldInfo.FieldType);
                    }
                }
                getValueILGenerator.Emit(OpCodes.Ret);
                getValue_Label_List.Add(getValue_Label);
                #endregion
                #region SetValue
                /*
                L_00d2: ldloc.0 
                L_00d3: ldarg.3 
                L_00d4: unbox.any int32
                L_00d9: callvirt instance void  {PropertyType} {Type}::{Property}(int32)
                L_00de: ret 
                 */
                /*
                L_00df: ldloc.0 
                L_00e0: ldarg.3 
                L_00e1: castclass string
                L_00e6: callvirt instance void  {PropertyType} {Type}::{Property}(string)
                L_00eb: ret                  
                 */
                Label setValue_Label = setValueILGenerator.DefineLabel();
                setValueILGenerator.MarkLabel(setValue_Label);
                setValueILGenerator.Emit(OpCodes.Ldloc, setValue_local_0);
                setValueILGenerator.Emit(OpCodes.Ldarg_3);
                if (memberInfo is PropertyInfo)
                {
                    PropertyInfo propertyInfo = memberInfo as PropertyInfo;
                    if (propertyInfo.PropertyType.IsValueType)
                    {
                        setValueILGenerator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
                    }
                    else
                    {
                        setValueILGenerator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
                    }
                    setValueILGenerator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
                }
                else
                {
                    FieldInfo fieldInfo = memberInfo as FieldInfo;
                    if (fieldInfo.FieldType.IsValueType)
                    {
                        setValueILGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType);
                    }
                    else
                    {
                        setValueILGenerator.Emit(OpCodes.Castclass, fieldInfo.FieldType);
                    }
                    setValueILGenerator.Emit(OpCodes.Stfld, fieldInfo);
                }

                setValueILGenerator.Emit(OpCodes.Ret);
                
				setValue_Label_List.Add(setValue_Label);
                #endregion
            }

            #region GetValue
            /*
            L_004d: switch (L_0060, L_006f, L_0079)
             */
            getValueILGenerator.MarkLabel(getValue_Switch_Label);
            getValueILGenerator.Emit(OpCodes.Ldloc, getValue_local_num);
            getValueILGenerator.Emit(OpCodes.Switch, getValue_Label_List.ToArray());
            /*
            L_007f: ldarg.2 
            L_0080: newobj instance void [mscorlib]System.ArgumentOutOfRangeException::.ctor(string)
            L_0085: throw              
             */
            getValueILGenerator.MarkLabel(getValue_Exception_Label);
            getValueILGenerator.Emit(OpCodes.Ldarg_2);
            getValueILGenerator.Emit(OpCodes.Newobj, typeof(System.ArgumentOutOfRangeException).GetConstructor(new System.Type[] { typeof(string) }));
            getValueILGenerator.Emit(OpCodes.Throw);
            /*
            L_0000: ret 
            */
            // it seems no need.
            //getValueILGenerator.Emit(OpCodes.Ret);
            #endregion
            #region SetValue
            /*
            L_004d: switch (L_0060, L_006f, L_0079)
             */
            setValueILGenerator.MarkLabel(setValue_Switch_Label);
            setValueILGenerator.Emit(OpCodes.Ldloc, setValue_local_num);
            setValueILGenerator.Emit(OpCodes.Switch, setValue_Label_List.ToArray());
            /*
            L_007f: ldarg.2 
            L_0080: newobj instance void [mscorlib]System.ArgumentOutOfRangeException::.ctor(string)
            L_0085: throw              
             */
            setValueILGenerator.MarkLabel(setValue_Exception_Label);
            setValueILGenerator.Emit(OpCodes.Ldarg_2);
            setValueILGenerator.Emit(OpCodes.Newobj, typeof(System.ArgumentOutOfRangeException).GetConstructor(new System.Type[] { typeof(string) }));
            setValueILGenerator.Emit(OpCodes.Throw);
            /*
              L_0000: ret 
              */
            // it seems no need.
            //setValueILGenerator.Emit(OpCodes.Ret);
            #endregion

            Type createdType = typeBuilder.CreateType();

            return System.Activator.CreateInstance(createdType) as IObjectAccessor;
        }
    }
}
